/*
 * Copyright (c) 2022-2022 All rights Reserved.
 */

package com.driving.core.service.impl;

import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
import java.util.Set;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.driving.core.base.BaseServiceImpl;
import com.driving.core.entity.Coach;
import com.driving.core.entity.School;
import com.driving.core.entity.Student;
import com.driving.core.entity.StudentApply;
import com.driving.core.exception.DrivingException;
import com.driving.core.mapper.ICoachMapper;
import com.driving.core.mapper.ISchoolMapper;
import com.driving.core.mapper.IStudentApplyMapper;
import com.driving.core.mapper.IStudentMapper;
import com.driving.core.service.IStudentService;
import com.driving.core.service.dto.ApplyDTO;
import com.driving.core.service.dto.StudentDTO;
import com.driving.core.service.dto.SuggestDTO;
import com.driving.core.util.Constant;
import com.driving.core.util.Constant.Audience;
import com.driving.core.util.JsonWebToken;
import com.driving.core.util.MessageType.ExceptionType;
import com.driving.core.util.MessageType.InformationType;
import com.driving.core.util.PasswordEncryption;

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * @author SAGITTAR
 * @since 2017
 */
@Slf4j
@AllArgsConstructor
@Service(value = "studentService")
public class StudentServiceImpl extends BaseServiceImpl<IStudentMapper, Student> implements IStudentService {

    private final ICoachMapper coachMapper;

    private final IStudentApplyMapper studentApplyMapper;

    private final ISchoolMapper schoolMapper;

    @Override
    public boolean passwordUpdateOrReset(StudentDTO dto, String token) {
        int studentId = getCorrespondingIdByTokenOrMobile(baseMapper, token, dto.getMobile(), dto.getOldPassword());
        Student student = new Student(studentId);
        try {
            student.setPassword(PasswordEncryption.getEncryptedPassword(dto.getPassword()));
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            log.error(e.getLocalizedMessage(), e);
        }
        return updateById(student);
    }

    @Override
    public Map<String, Object> register(StudentDTO studentDTO) {
        if (Objects.nonNull(baseMapper.selectByMobile(studentDTO.getMobile()))) {
            throw new DrivingException(configReader.getExceptionMessage(ExceptionType.USER_EXIST));
        }
        Student student = studentDTO.toStudent();
        Map<String, Object> result = new HashMap<>(2);
        if (save(student)) {
            result.put(Constant.NAME, student.getNickName());
        }
        return result;
    }

    @Override
    public Map<String, String> login(StudentDTO studentDTO) {
        Student student = baseMapper.selectByMobile(studentDTO.getMobile());
        if (Objects.isNull(student)) {
            throw new DrivingException(configReader.getExceptionMessage(ExceptionType.USER_NOT_FOUND));
        }
        String encryptionPwd = student.getPassword();
        if (!validatePassword(studentDTO.getPassword(), encryptionPwd)) {
            throw new DrivingException(configReader.getExceptionMessage(ExceptionType.INCORRECT_PASSWORD));
        }
        Map<String, String> loginResult = new HashMap<>(4);
        loginResult.put(Constant.NAME, student.getNickName());
        String compact = JsonWebToken.compact(student.getId(), Audience.STUDENT, student.getMobile(), encryptionPwd);
        loginResult.put(Constant.TOKEN, compact);
        return loginResult;
    }

    @Override
    public boolean suggest(SuggestDTO suggestDTO) {
        return suggestDTO.toSuggest().insert();
    }

    @Override
    public boolean apply(ApplyDTO applyDTO) {
        LambdaQueryWrapper<StudentApply> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(StudentApply::getStudentId, applyDTO.getStudentId());
        List<StudentApply> studentApplyList = studentApplyMapper.selectList(wrapper);
        if (Objects.nonNull(studentApplyList) && !studentApplyList.isEmpty()) {
            throw new DrivingException(configReader.getExceptionMessage(ExceptionType.APPLY_ALWAYS_EXISTS));
        }
        if (Objects.nonNull(applyDTO.getCoachId())) {
            Coach coach = coachMapper.selectById(applyDTO.getCoachId());
            if (Objects.isNull(coach)) {
                throw new DrivingException(configReader.getExceptionMessage(ExceptionType.COACH_NOT_FOUND));
            }
        }
        // 防止同时报名
        synchronized (applyDTO.getStudentId().toString().intern()) {
            return applyDTO.toStudentApply().insert();
        }
    }

    @Override
    public Map<String, String> myCoach(int studentId) {
        LambdaQueryWrapper<StudentApply> applyEntityWrapper = new LambdaQueryWrapper<>();
        applyEntityWrapper.eq(StudentApply::getStudentId, studentId);
        List<StudentApply> studentApplyList = studentApplyMapper.selectList(applyEntityWrapper);
        if (Objects.isNull(studentApplyList) || studentApplyList.isEmpty()) {
            throw new DrivingException(configReader.getInfoMessage(InformationType.NOT_APPLY_EXISTS));
        }
        Coach coach = coachMapper.selectById(studentApplyList.get(0).getCoachId());
        return Collections.singletonMap("name", coach.getName());
    }

    private List<School> selectByAreaName(String areaName) {
        QueryWrapper<School> schoolWrapper = new QueryWrapper<>();
        schoolWrapper.like("small_area", areaName);
        return schoolMapper.selectList(schoolWrapper);
    }

    @Override
    public Map<String, Object> selectSchoolListByAreaName(String areaName) {
        // 加载几个驾校和教练
        int count = 3;
        List<School> schoolList = selectByAreaName(areaName);
        List<School> resultList = new ArrayList<>(count);
        for (int id : getRandomIdList(schoolList.size(), count)) {
            resultList.add(schoolList.get(id));
        }
        List<Map<String, Object>> schoolMapList = resultList.stream().map(school -> {
            Map<String, Object> map = new HashMap<>(8);
            map.put("id", school.getId());
            map.put("name", school.getName());
            map.put("detail", school.getDetail());
            map.put("coverUrl", school.getCoverUrl());
            return map;
        }).collect(Collectors.toList());
        return Collections.singletonMap("list", schoolMapList);
    }

    /**
     * 生成随机数集合，不重复，有序
     *
     * @param total 总数
     * @param count 生成个数
     * @return 成随机数集合，不重复，有序
     */
    private List<Integer> getRandomIdList(int total, int count) {
        if (total <= 0 || count <= 0) {
            return Collections.emptyList();
        }
        if (total <= count) {
            List<Integer> randomIdList = new ArrayList<>(total);
            for (int index = 1; index <= total; index++) {
                randomIdList.add(index);
            }
            return randomIdList;
        }
        Random random = new Random();
        Set<Integer> randomSet = new HashSet<>(count);
        do {
            randomSet.add(random.nextInt(total));
        } while (randomSet.size() < count);
        List<Integer> randomIdList = new ArrayList<>(randomSet);
        Collections.sort(randomIdList);
        return randomIdList;
    }

    @Override
    public Map<String, Object> selectCoachListByAreaName(String areaName) {
        List<School> schoolList = selectByAreaName(areaName);
        QueryWrapper<Coach> coachWrapper = new QueryWrapper<>();
        coachWrapper.in("school_id", schoolList.stream().map(School::getId).collect(Collectors.toList()));
        List<Coach> coachList = coachMapper.selectList(coachWrapper);

        int count = 3;
        List<Coach> resultList = new ArrayList<>(count);
        for (int id : getRandomIdList(coachList.size(), count)) {
            resultList.add(coachList.get(id));
        }
        List<Map<String, Object>> coachMapList = resultList.stream().map(coach -> {
            Map<String, Object> map = new HashMap<>(16);
            map.put("id", coach.getId());
            map.put("name", coach.getName());
            map.put("gender", coach.getGender().getDescription());
            return map;
        }).collect(Collectors.toList());
        Map<String, Object> coachMap = new HashMap<>(4);
        coachMap.put("list", coachMapList);
        return coachMap;
    }

    @Override
    public Map<String, Object> selectSchoolDetailBySchoolId(int schoolId) {
        Map<String, Object> schoolMap = new HashMap<>(8);
        School school = schoolMapper.selectById(schoolId);
        schoolMap.put("name", school.getName());
        schoolMap.put("price", school.getPrice());
        schoolMap.put("detail", school.getDetail());
        // 教练
        QueryWrapper<Coach> coachWrapper = new QueryWrapper<>();
        coachWrapper.eq("school_id", schoolId);
        List<Coach> coachList = coachMapper.selectList(coachWrapper);
        List<Map<String, Object>> coachListMapInSchool = null;
        if (Objects.nonNull(coachList) && !coachList.isEmpty()) {
            coachListMapInSchool = coachList.stream().map(coach -> {
                Map<String, Object> map = new HashMap<>();
                map.put("id", coach.getId());
                map.put("name", coach.getName());
                map.put("gender", coach.getGender().getDescription());
                return map;
            }).collect(Collectors.toList());
        }
        schoolMap.put("coachList", coachListMapInSchool);
        return schoolMap;
    }
}
